package org.jeecg.modules.cosmetic.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.util.Units;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlCursor;
import org.jeecg.common.util.oss.OssBootUtil;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.rmi.RemoteException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.apache.poi.xssf.usermodel.XSSFFont.DEFAULT_FONT_SIZE;

/**
 * @author scx
 * @date 2022/7/21 3:00 下午
 */
@Slf4j
public class XwpfTUtil {

    /**
     * 被list替换的段落 被替换的都是oldParagraph
     */
    private XWPFParagraph oldParagraph;
    /**
     * 模板对象
     */
    private XWPFDocument document;


    /**
     * 生成word
     *
     * @param filePath
     * @param paramMap
     * @param tableIndex
     * @param tables
     * @throws IOException
     * @throws InvalidFormatException
     */
    public static String process(String filePath, Map<String, Object> paramMap, int[] tableIndex, List<List<String[]>> tables) throws IOException, InvalidFormatException {
        XwpfTUtil xwpfTUtil = new XwpfTUtil();
        return xwpfTUtil.createWord(filePath, paramMap, tableIndex, tables);
    }

    /**
     * 创建模版
     *
     * @param filePath
     * @param params     待填充的数据 params.put("key",value) 文档中对应为 ${key}
     * @param tableIndex 表格下标，从0开始，如Word中3个表格，仅对1、3个表格进行修改，参数为 int[]{0,2}
     * @param tables
     * @throws IOException
     * @throws InvalidFormatException
     */
    public String createWord(String filePath, Map<String, Object> params, int[] tableIndex, List<List<String[]>> tables) throws IOException, InvalidFormatException {
        Resource resource = new ClassPathResource("template/安评报告模版.docx");
        InputStream in = resource.getInputStream();
        document = new XWPFDocument(OPCPackage.open(in));
        replaceParams(params);
        if (tableIndex.length != tables.size()) throw new RemoteException("表格下标数量与表格参数数量不一致！");
        for (int i = 0; i < tableIndex.length; i++) {
            changeTable(params, tableIndex[i], tables.get(i));
        }
        ByteArrayOutputStream ba = new ByteArrayOutputStream();
        document.write(ba);
        close(in);
        close(ba);
        InputStream inputStream = new ByteArrayInputStream(ba.toByteArray());
        return OssBootUtil.upload(inputStream, filePath);
    }

    /**
     * 替换段落里面的变量
     *
     * @param params 参数
     */
    private void replaceParams(Map<String, Object> params) {
        List<IBodyElement> elements = document.getBodyElements();
        for (int i = 0; i < elements.size(); i++) {
            IBodyElement element = elements.get(i);
            // 普通段落
            if (element instanceof XWPFParagraph) {
                XWPFParagraph paragraph = (XWPFParagraph) element;
                oldParagraph = paragraph;
                if (paragraph.getParagraphText().isEmpty()) {
                    continue;
                }
                replaceParam(paragraph, params);
            }
        }
    }

    /**
     * @param params     需要替换的信息集合
     * @param tableIndex 第几个表格
     * @param tableList  需要插入的表格信息集合
     */
    public void changeTable(Map<String, Object> params, int tableIndex, List<String[]> tableList) {
        //获取表格对象集合
        List<XWPFTable> tables = document.getTables();
        //根据实际模板情况 决定去第几个word中的表格
        XWPFTable table = tables.get(tableIndex);
        //替换表格中的参数
        replaceTableParams(document, params);
        //在表格中插入数据
        insertTable(table, tableList, tableIndex);

    }

    /**
     * 替换表格里面的变量
     *
     * @param doc    要替换的文档
     * @param params 参数
     */
    private void replaceTableParams(XWPFDocument doc, Map<String, Object> params) {
        Iterator<XWPFTable> iterator = doc.getTablesIterator();
        XWPFTable table;
        List<XWPFTableRow> rows;
        List<XWPFTableCell> cells;
        List<XWPFParagraph> paras;
        while (iterator.hasNext()) {
            table = iterator.next();
            //判断表格是需要替换还是需要插入，判断逻辑有$为替换，表格无$为插入
            if (!matcher(table.getText()).find()) {
                return;
            }
            //获取所有行
            rows = table.getRows();
            for (XWPFTableRow row : rows) {
                //获取所有列
                cells = row.getTableCells();
                for (XWPFTableCell cell : cells) {
                    paras = cell.getParagraphs();
                    for (XWPFParagraph para : paras) {
                        replaceParam(para, params);
                    }
                }
            }
        }
    }

    /**
     * 为表格插入行数，此处不处理表头，所以从第二行开始
     *
     * @param table     需要插入数据的表格
     * @param tableList 插入数据集合
     */
    private static void insertTable(XWPFTable table, List<String[]> tableList, int tableIndex) {
        //创建与数据一致的行数
        for (int i = 0; i < tableList.size(); i++) {
            table.createRow();
        }
        int length = table.getRows().size();
        for (int i = 1; i < length; i++) {
            XWPFTableRow newRow = table.getRow(i);
            List<XWPFTableCell> cells = newRow.getTableCells();
            for (int j = 0; j < cells.size(); j++) {
                XWPFTableCell cell = cells.get(j);
                //默认设置水平居中
                CTTc cttc = cell.getCTTc();
                CTTcPr ctPr = cttc.addNewTcPr();
                ctPr.addNewVAlign().setVal(STVerticalJc.CENTER);
                cttc.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);

                CTP ctP = (cttc.sizeOfPArray() == 0) ? cttc.addNewP() : cttc.getPArray(0);
                addBreakInCell(ctP, cell, tableList.get(i - 1)[j]);
            }
        }

        //获取需要合并行的下标
        List<Integer[]> indexList = startEnd(tableList);
        for (int c = 0; c < indexList.size(); c++) {
            //合并行
            mergeCellVertically(table, 0, indexList.get(c)[0] + 1, indexList.get(c)[1] + 1);
            //第一个表格（配方表）第三列也需要合并
            if (tableIndex == 2) {
                mergeCellVertically(table, 3, indexList.get(c)[0] + 1, indexList.get(c)[1] + 1);
            }
        }

    }

    /**
     * 换行处理
     *
     * @param ctP
     * @param cell
     * @param text
     */
    private static void addBreakInCell(CTP ctP, XWPFTableCell cell, String text) {
        XWPFParagraph par = new XWPFParagraph(ctP, cell);
        XWPFRun run = par.createRun();
        CTFonts font = run.getCTR().addNewRPr().addNewRFonts();
        //改变数字或者英文字体设置这个
        font.setAscii("Times New Roman");
        if (StringUtils.isEmpty(text)) {
            run.setText(text);
            return;
        }
        if (text.contains("\n")) {
            par.setAlignment(ParagraphAlignment.LEFT);
            String[] lines = text.split("\n");
            if (lines.length > 0) {
                run.setText(lines[0], 0);
                for (int i = 1; i < lines.length; i++) {
                    run.addBreak();
                    run.setText(lines[i]);
                }
            }
        } else {
            run.setText(text);
        }
    }

    /**
     * 替换段落里面的变量
     *
     * @param paragraph 要替换的段落
     * @param params    参数
     */
    private void replaceParam(XWPFParagraph paragraph, Map<String, Object> params) {
        List<XWPFRun> runs = paragraph.getRuns();
        for (XWPFRun run : runs) {
            String runString = run.toString();
            Matcher m = matcher(runString);
            if (!m.find()) {
                continue;
            }
            boolean isSetText = false;
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                String key = entry.getKey();
                if (runString.indexOf(key) != -1) {
                    isSetText = true;
                    Object obj = entry.getValue();
                    //图片处理
                    if (key.equalsIgnoreCase("${resumeImage}")) {
                        addPicture(run, String.valueOf(obj));
                        runString = runString.replace(key, "");
                        continue;
                    }
                    //处理一个参数多条数据
                    if (obj instanceof List) {
                        runString = delDynList(runString, (List) obj);
                    } else {
                        runString = runString.replace(key, String.valueOf(obj));
                    }
                }
            }
            if (isSetText) {
                run.setText(runString, 0);
            }
        }

    }

    /**
     * 添加图片
     *
     * @param run
     * @param imageUrl
     */
    private void addPicture(XWPFRun run, String imageUrl) {
        if (StringUtils.isEmpty(imageUrl)) {
            log.error("图片URL为空，无法插入图片");
            return;
        }
        // 该段落是图片占位符
        URL url = null;
        try {
            String imageSuffix = imageUrl.substring(imageUrl.lastIndexOf(".") + 1);
            ImageTypeEnum imageTypeEnum = ImageTypeEnum.getTypeIdByName(imageSuffix);
            url = new URL(imageUrl);
            InputStream in = url.openStream();
//            BufferedImage image = ImageIO.read(url);
            run.addPicture(in, imageTypeEnum.getTypeId(), "",
                    Units.pixelToEMU(617), Units.pixelToEMU(725));
        } catch (InvalidFormatException e) {
            log.error("图片格式错误:{}", e);
        } catch (IOException e) {
            log.error("添加简历图片IO异常:{}", e);
        }
    }

    /**
     * 处理的动态的段落（参数为list）
     *
     * @param placeholder 段落占位符
     * @param obj
     * @return
     */
    private String delDynList(String placeholder, List obj) {
        String placeholderValue = placeholder;
        List dataList = obj;
        for (int i = 0, size = dataList.size(); i < size; i++) {
            Object text = dataList.get(i);
            // 占位符的那行, 不用重新创建新的行
            if (i == 0) {
                placeholderValue = String.valueOf(text);
            } else {
                XWPFParagraph paragraph = createParagraph(String.valueOf(text));
                if (paragraph != null) {
                    oldParagraph = paragraph;
                }
            }
        }
        return placeholderValue;
    }

    /**
     * 复制段落
     *
     * @param sourcePar 原段落
     * @param targetPar
     * @param texts
     */
    private void copyParagraph(XWPFParagraph sourcePar, XWPFParagraph targetPar, String... texts) {
        // 设置布局
        targetPar.setAlignment(sourcePar.getAlignment());
        targetPar.setVerticalAlignment(sourcePar.getVerticalAlignment());

        if (texts != null && texts.length > 0) {
            String[] arr = texts;
            XWPFRun xwpfRun = sourcePar.getRuns().size() > 0 ? sourcePar.getRuns().get(0) : null;

            for (int i = 0, len = texts.length; i < len; i++) {
                String text = arr[i];
                XWPFRun run = targetPar.createRun();
                run.setText(text);
                run.setFontFamily(xwpfRun.getFontFamily());
                int fontSize = xwpfRun.getFontSize();
                run.setFontSize((fontSize == -1) ? DEFAULT_FONT_SIZE : fontSize);
                run.setBold(xwpfRun.isBold());
                run.setItalic(xwpfRun.isItalic());
            }
        }
    }

    /**
     * 创建段落 <p></p>
     *
     * @param texts
     */
    public XWPFParagraph createParagraph(String... texts) {

        // 使用游标创建一个新行
        XmlCursor cursor = oldParagraph.getCTP().newCursor();
        XWPFParagraph newPar = document.insertNewParagraph(cursor);
        // 设置段落样式
        newPar.getCTP().setPPr(oldParagraph.getCTP().getPPr());
        copyParagraph(oldParagraph, newPar, texts);
        return newPar;
    }

    /**
     * 合并行
     *
     * @param table
     * @param col     需要合并的列
     * @param fromRow 开始行
     * @param toRow   结束行
     */
    public static void mergeCellVertically(XWPFTable table, int col, int fromRow, int toRow) {
        for (int rowIndex = fromRow; rowIndex <= toRow; rowIndex++) {
            CTVMerge vmerge = CTVMerge.Factory.newInstance();
            if (rowIndex == fromRow) {
                vmerge.setVal(STMerge.RESTART);
            } else {
                vmerge.setVal(STMerge.CONTINUE);
            }
            XWPFTableCell cell = table.getRow(rowIndex).getCell(col);
            CTTcPr tcPr = cell.getCTTc().getTcPr();
            if (tcPr != null) {
                tcPr.setVMerge(vmerge);
            } else {
                tcPr = CTTcPr.Factory.newInstance();
                tcPr.setVMerge(vmerge);
                cell.getCTTc().setTcPr(tcPr);
            }
        }
    }

    /**
     * 获取需要合并单元格的下标
     * p>根据相同列的值进行合并
     *
     * @return
     */
    public static List<Integer[]> startEnd(List<String[]> daList) {
        List<Integer[]> indexList = new ArrayList<Integer[]>();

        List<String> list = new ArrayList<String>();
        for (int i = 0; i < daList.size(); i++) {
            list.add(daList.get(i)[0]);
        }
        Map<Object, Integer> tm = new HashMap<Object, Integer>();
        for (int i = 0; i < daList.size(); i++) {
            if (!tm.containsKey(daList.get(i)[0])) {
                tm.put(daList.get(i)[0], 1);
            } else {
                int count = tm.get(daList.get(i)[0]) + 1;
                tm.put(daList.get(i)[0], count);
            }
        }
        for (Map.Entry<Object, Integer> entry : tm.entrySet()) {
            String key = entry.getKey().toString();
            if (list.indexOf(key) != (-1)) {
                Integer[] index = new Integer[2];
                index[0] = list.indexOf(key);
                index[1] = list.lastIndexOf(key);
                indexList.add(index);
            }
        }
        return indexList;
    }

    /**
     * 正则匹配字符串
     *
     * @param str
     * @return
     */
    private static Matcher matcher(String str) {
        Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(str);
        return matcher;
    }

    /**
     * 关闭输入流
     *
     * @param is
     */
    private static void close(InputStream is) {
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 关闭输出流
     *
     * @param os
     */
    private static void close(OutputStream os) {
        if (os != null) {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}